//
//  GameData.m
//  Agility
//
//  Created by Dominique d'Argent on 15.03.11.
//  Copyright 2011 Nicky Nubbel. All rights reserved.
//

#import "GameData.h"

// import game objects
#import "GameObject.h"
#import "PlayerObject.h"
#import "TurretObject.h"
#import "ShotObject.h"
#import "ContrailObject.h"

// import game object layer
#import "GameObjectLayer.h"

NSString *GAME_OBJECT_NEW_NOTIFICATION          = @"GameObjectNewNotification";
NSString *GAME_SCENERY_OBJECT_NEW_NOTIFICATION  = @"GameSceneryObjectNewNotification";
NSString *GAME_OVER_NOTIFICATION                = @"GameOverNotification";

NSString *GAME_PLAYER_KEY       = @"player";
NSString *GAME_TURRET_KEY_BASE  = @"turret";
NSString *GAME_SHOT_KEY_BASE    = @"shot";
NSString *GAME_SCENERY_KEY_BASE = @"scenery";

const NSTimeInterval GAME_UPDATE_DURATION = 1.0 / 30.0;

static GameData *singletonInstance = nil;

@implementation GameData

@synthesize state;
@synthesize size;
@synthesize gameObjects;
@synthesize touchPoint;
@synthesize score;

//
// sharedGameData
//
// Returns the singleton instance of this class.
//
+ (GameData *)sharedGameData {
    @synchronized(self) {    
        if (!singletonInstance) {
            singletonInstance = [[self alloc] init];
        }
    }
    
    return singletonInstance;
}

#pragma mark - Object life cycle

//
// init
//
// Initializes game state and adds obersers.
//
- (id)init {
    self = [super init];
    
    if (self) {
        self.state = GameStateReady;
        
        gameObjects = [[NSMutableDictionary alloc] init];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(gameOver) 
                                                     name:GAME_OVER_NOTIFICATION
                                                   object:nil];
    }
    
    return self;
}

//
// dealloc
//
// Realeses retained objects and removes observers.
//
- (void)dealloc {
    [gameObjects release];
    [lastUpdate release];
    
    
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:GAME_OVER_NOTIFICATION
                                                  object:nil];
    
    [super dealloc];
}

#pragma mark - Property accessors

//
// size
//
// Returns the available screen size for the game.
//
- (CGSize)size {
    if (CGSizeEqualToSize(size, CGSizeZero)) {
        size = [UIScreen mainScreen].applicationFrame.size;
    }
    
    return size;
}

#pragma mark - Game Objects

//
// addGameObject:forKey:
//
// Adds a game object identified by a key to the |gameObjects| dictionary 
// and posts a notification.
//
- (void)addGameObject:(GameObject *)newGameObject forKey:(NSString *)gameObjectKey {
    newGameObject.gameObjectKey = gameObjectKey;
    
    [gameObjects setValue:newGameObject forKey:gameObjectKey];
    
    [[NSNotificationCenter defaultCenter] postNotificationName:GAME_OBJECT_NEW_NOTIFICATION
                                                        object:gameObjectKey];
    
}

//
// removeGameObjectForKey:
//
// Removes a game object identified by its key from the |gameObjects] dictionary.
//
- (void)removeGameObjectForKey:(NSString *)gameObjectKey {
    [gameObjects removeObjectForKey:gameObjectKey];
}

//
// gameObjectForKey:
//
// Returns the game object for a given key.
//
- (GameObject *)gameObjectForKey:(NSString *)gameObjectKey {
    return [gameObjects objectForKey:gameObjectKey];
}

//
// keyForTurretAtIndex:
//
// Composes a key for a turrent specified by an index using the key base.
//
- (NSString *)keyForTurretAtIndex:(NSUInteger)index {
    return [NSString stringWithFormat:@"%@-%d", GAME_TURRET_KEY_BASE, index];
}

//
// keyForNextShot
//
// Composes the key for the next shot object using a key base.
//
- (NSString *)keyForNextShot {
    static NSUInteger shotIndex = 0;
    
    return [NSString stringWithFormat:@"%@-%d", GAME_SHOT_KEY_BASE, shotIndex++];
}

////
//// keyForNextContrail
////
//// Composes the key for the next contrail object using a key base.
////
//- (NSString *)keyForNextSceneryObject {
//    static NSUInteger sceneryIndex = 0;
//    
//    return [NSString stringWithFormat:@"%@-%d", GAME_SCENERY_KEY_BASE, sceneryIndex++];
//}

- (void)detectAndHandleCollisions {
    NSArray *allKeys = [gameObjects allKeys];
    NSMutableArray *testKeys = [allKeys mutableCopy];
    
	for (NSString *gameObjectKey in allKeys) {
        // remove current object in order to not test it against itself
        [testKeys removeObject:gameObjectKey];
        
        GameObject *gameObject = [self gameObjectForKey:gameObjectKey];
        
        // continue if the game object is within spawn time
        if (gameObject.lifeTime <= gameObject.spawnTime) {
            continue;
        }
        
        NSMutableArray *collidedObjectKeys = [[NSMutableArray alloc] init];
        
        for (NSString *testObjectKey in testKeys) {
            GameObject *testObject = [self gameObjectForKey:testObjectKey];
            
            // continue if the test object is within spawn time
            if (testObject.lifeTime <= testObject.spawnTime) {
                continue;
            }
            
            // detect collision
            if ([gameObject collidesWithGameObject:testObject]) {
                // notify game objects of collision 
                if ([gameObject didCollideWithGameObject:testObject]) {
                    [collidedObjectKeys addObject:gameObjectKey];
                }
                
                if ([testObject didCollideWithGameObject:gameObject]) {
                    [collidedObjectKeys addObject:testObjectKey];
                }
            }
        }
        
        // remove collided objects
        [gameObjects removeObjectsForKeys:collidedObjectKeys];
        
        // KVO
        for (NSString *collidedObjectKey in collidedObjectKeys) {
            [gameObjects willChangeValueForKey:gameObjectKey];
            [gameObjects didChangeValueForKey:collidedObjectKey];
        }
        
        [collidedObjectKeys release];
	}
    
    [testKeys release];
}

#pragma mark - Scenery objects
//
// addSceneryObject:
//
// Posts notification.
//
- (void)addSceneryObject:(SceneryObject *)newSceneryObject {
    [[NSNotificationCenter defaultCenter] 
        postNotificationName:GAME_SCENERY_OBJECT_NEW_NOTIFICATION
                      object:newSceneryObject];
}

#pragma mark - Game loop

//
// start
//
// Starts the game loop.
//
- (void)start {
    // clear game objects' dictionary
    [gameObjects removeAllObjects];
    
    // spawn player object
    [PlayerObject spawnPlayer];
    
    // spawn enemies
    [TurretObject spawnTurretWithIndex:0];
    [TurretObject spawnTurretWithIndex:1];
//    
//    [ShotObject spawnShotAtPosition:CGPointMake(100, 390) withTarget:nil trajectory:M_PI_2];
//    [ShotObject spawnShotAtPosition:CGPointMake(700, 400) withTarget:nil trajectory:-M_PI_2];
//    
//    [ContrailObject spawnAtPosition:CGPointMake(100, 100)];
    
    [lastUpdate release];
    lastUpdate = nil;
    
    // reset score
    self.score = 0.0;
    
    self.state = GameStateRunning;
    
    timer = [NSTimer scheduledTimerWithTimeInterval:GAME_UPDATE_DURATION 
                                             target:self 
                                           selector:@selector(update) 
                                           userInfo:nil 
                                            repeats:YES];
}

//
// stop
//
// Stops the game loop.
//
- (void)stop {
    [timer invalidate];
    timer = nil;
    
    self.state = GameStateReady;
}

//
// update
//
// Updates the game state in a specified time interval.
//
- (void)update {
    NSDate *now = [[NSDate alloc] init];
    NSTimeInterval frameDuration;
    
    // calculate frame duration
    if (lastUpdate) {
        frameDuration = [now timeIntervalSinceDate:lastUpdate];
        [lastUpdate release];
    } else {
        frameDuration = GAME_UPDATE_DURATION;
    }
    
    lastUpdate = now;
    
    // update score
    if (state == GameStateRunning) {
        self.score += frameDuration;
    }
    
    // collision detection
    [self detectAndHandleCollisions];
    
    // update game objects
	NSArray *allKeys = [gameObjects allKeys];
    
	for (NSString *gameObjectKey in allKeys) {
		GameObject *gameObject = [self gameObjectForKey:gameObjectKey];
		
        BOOL gameObjectWillDie = [gameObject updateWithTimeInterval:frameDuration];
        
        // KVO
        if ([gameObject hasChanges]) {
            [gameObjects willChangeValueForKey:gameObjectKey];
            [gameObjects didChangeValueForKey:gameObjectKey];
            
            [gameObject resetChanges];
        }
        
		if (gameObjectWillDie) {
			[self removeGameObjectForKey:gameObjectKey];
		}
	}
    
//    NSLog(@"fps: %.5f; %d game objects", 1.0 / frameDuration, [allKeys count]);
}

//
// gameOver
//
// 
// Called when a |GAME_OVER_NOTIFICATION| has been received.
//
- (void)gameOver {
    self.state = GameStateStopped;
}

@end
